#pragma once

#include <RcppArmadillo.h>
using namespace Rcpp;

#include <array>

template <typename Arg1, typename... Args>
inline R_xlen_t check_length(const Arg1 &arg1, const Args &...args)
{
    std::array<R_xlen_t, sizeof...(Args)> n{args.size()...};
    R_xlen_t n0 = arg1.size();
    for (size_t i = 0; i < n.size(); ++i)
    {
        if (n0 != n[i])
        {
            return 0;
        }
    }
    return n0;
}

template <bool aggrtime, typename T, typename Tbin, typename Tout = NumericVector>
struct aggr
{
    static constexpr auto bin_name()
    {
        if constexpr (aggrtime)
        {
            return std::string{"time"};
        }
        else
        {
            return std::string{"nbin"};
        }
    }

    template <typename... Args>
    static SEXP create(Args... args)
    {
        T *ptr = new T(args...);
        return XPtr<T>(ptr, true);
    }

    template <typename Arg1, typename... Args>
    static List run_ohlcv1(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = check_length(arg1, args...);
        if (!n)
        {
            return List::create();
        }

        XPtr<T> xptr(ptr);
        Tbin bin(n);
        Tout vwap(n);
        n = xptr->operator()(arg1.begin(), arg1.end(), args.begin()...,
                             bin.begin(), vwap.begin());
        if (n)
        {
            Range range{0, n - 1};
            return List::create(
                _[bin_name()] = bin[range],
                _["vwap"] = vwap[range]);
        }
        else
        {
            return List::create();
        }
    }

    template <typename Arg1, typename... Args>
    static List run_ohlcv3(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = check_length(arg1, args...);
        if (!n)
        {
            return List::create();
        }

        XPtr<T> xptr(ptr);
        Tbin bin(n);
        Tout vol(n), tnvr(n), vwap(n);
        n = xptr->operator()(arg1.begin(), arg1.end(), args.begin()...,
                             bin.begin(), vol.begin(), tnvr.begin(), vwap.begin());
        if (n)
        {
            Range range{0, n - 1};
            return List::create(
                _[bin_name()] = bin[range],
                _["vol"] = vol[range],
                _["tnvr"] = tnvr[range],
                _["vwap"] = vwap[range]);
        }
        else
        {
            return List::create();
        }
    }

    template <typename Arg1, typename... Args>
    static List run_ohlcv5(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = check_length(arg1, args...);
        if (!n)
        {
            return List::create();
        }

        XPtr<T> xptr(ptr);
        Tbin bin(n);
        Tout o(n), h(n), l(n), c(n), vwap(n);
        n = xptr->operator()(arg1.begin(), arg1.end(), args.begin()...,
                             bin.begin(), o.begin(), h.begin(), l.begin(), c.begin(), vwap.begin());
        if (n)
        {
            Range range{0, n - 1};
            return List::create(
                _[bin_name()] = bin[range],
                _["open"] = o[range],
                _["high"] = h[range],
                _["low"] = l[range],
                _["close"] = c[range],
                _["vwap"] = vwap[range]);
        }
        else
        {
            return List::create();
        }
    }

    template <typename Arg1, typename... Args>
    static List run_ohlcv7(SEXP ptr, const Arg1 &arg1, const Args &...args)
    {
        R_xlen_t n = check_length(arg1, args...);
        if (!n)
        {
            return List::create();
        }

        XPtr<T> xptr(ptr);
        Tbin bin(n);
        Tout o(n), h(n), l(n), c(n), vol(n), tnvr(n), vwap(n);
        n = xptr->operator()(arg1.begin(), arg1.end(), args.begin()...,
                             bin.begin(), o.begin(), h.begin(), l.begin(), c.begin(),
                             vol.begin(), tnvr.begin(), vwap.begin());
        if (n)
        {
            Range range{0, n - 1};
            return List::create(
                _[bin_name()] = bin[range],
                _["open"] = o[range],
                _["high"] = h[range],
                _["low"] = l[range],
                _["close"] = c[range],
                _["vol"] = vol[range],
                _["tnvr"] = tnvr[range],
                _["vwap"] = vwap[range]);
        }
        else
        {
            return List::create();
        }
    }
};
